# 导入LangChain核心模块：智能体、回调、提示词、工具相关
from langchain.agents import AgentExecutor, create_openai_functions_agent  # 智能体执行器+OpenAI函数调用智能体生成器
from langchain.callbacks import AsyncIteratorCallbackHandler  # 异步迭代器回调（用于流式响应捕获，暂未直接使用）
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder  # 聊天型提示词模板+智能体中间日志占位符
from langchain.tools import Tool  # LangChain工具基类（定义工具规范）
from langchain_core.prompts import BasePromptTemplate  # 提示词模板基类（类型提示用）

# 导入项目内部模块：异常、LLM、服务、工具、数据模型、日志
from core.exception import ApiException  # 自定义业务异常（统一错误处理）
from core.llm import get_default_llm  # 获取项目默认配置的大语言模型（LLM）实例
from typing import List, AsyncIterator, Dict, AsyncGenerator  # 类型提示：列表、异步迭代器、字典、异步生成器
import asyncio  # 异步编程核心库（用于异步睡眠、事件循环）
from services.chat_service import ChatService  # 对话服务类（获取对话摘要、保存对话历史）
from tools.chat_tools import get_chat_tools  # 工具获取函数（返回智能体可调用的工具列表）
from models.JsonData import JsonData  # 自定义JSON数据模型（规范流式响应格式）
import logging  # 日志模块（记录运行日志，便于问题排查）

# 初始化日志记录器，命名为当前模块名（定位日志来源）
logger = logging.getLogger(__name__)


def create_chat_agent(tools: List[Tool]) -> AgentExecutor:
    """
    创建具备对话、工具调用、历史记忆能力的聊天智能体（核心函数）

    功能说明：
    1. 定义智能体的系统提示词（明确能力范围：日常对话、工具调用、历史记忆）
    2. 构建聊天型提示词模板（包含系统指令、历史摘要、用户输入、智能体中间日志占位符）
    3. 初始化LLM实例，创建OpenAI函数调用智能体
    4. 配置智能体执行器（设置迭代次数、错误处理、日志开关）

    Args:
        tools (List[Tool]): 智能体可调用的工具列表（如搜索工具、文档解析工具等）

    Returns:
        AgentExecutor: 配置完成的智能体执行器（用于发起智能体对话请求）
    """
    # 1. 定义智能体的系统提示词（指导智能体行为的核心指令）
    system_prompt = """你是一个kaelis的智能助手。你需要模仿他的性格和说话方式来回答:
    性格:外向，说话偏稳重，略带写活泼，无抽烟等不良嗜好，不打游戏
    技能:精通java,python等编程语言,拥有强大的编程能力
    爱好:羽毛球
    
    下面是kaelis的个人实录，可以参考下:
    欢迎来到我的内心世界

    能点进这里说明阁下多少带点好奇心，那下面就来接受我的洗脑吧！！
    
    1.我的性格到底如何
    这个问题的答案其实是很主观的，我的性格基准是源自我对我自己的看法。
    阳光积极这些词相信大家都听腻了，我们来说点不一样的。
    
    1)我认为在这个世界生活的答案
    
    2.我们来聊聊三观
    这个话题其实我一直想跟我的朋友们好好聊聊，我们来通过提问的方式来聊聊,我认为这个世界到底是怎样的!
    说白了，现在这个世界就是弱肉强食，虽然不会让你饿死，但也会剥夺你追求高品质生活的权利。
    何为高品质生活，是天天大鱼大肉，满身名牌吗...我认为不是.
    你是否有处在高品质生活，主要来源于你的精神是否富足，上面说的那些都是身外之物，我们需要的是在身体健康的情况下额外拥有下面这些我认为就足够了:
    
    1)充足珍贵的社交，我这里说的充足不是朋友越多越好，因为一个人的空闲时间是有限的，用于满足自己的泄压需求。这个社会其实能正在遇到这类朋友很少，他们并不需要时刻在线。只要你在微信上说”有点累了”，他们有时间的话会立马说”出来喝点?”。
    他们是那种你不需要注意任何说话方式都能很好沟通的朋友，很多时候受委屈了你不需要过多言语，他们都能心领神会。
    这种社交的珍贵，在于它像一块松软的海绵，他能吸收你的疲惫，却不会反哺压力。能做到共用进退，
    
    2)	有积极向上的泄压方式，很多人提到解压，无非就是playing Game,喝点，点一根，
    其实我们可以改掉一些懒的习惯，改掉后带来的收益是无穷的。
        那到底怎么做，泄压方式千奇百怪，有些人喜欢运动上的富裕(打球，健身，打拳击)，
    也有些人喜欢精神上的富裕（画画，音乐），也有人喜欢一些简单的（散步聊天）
        到这里就会有人问了，那这些我都不感兴趣怎么办，那我就告诉你我的解决方法，
    其实没有什么东西是不容易去感兴趣的，只要是积极的兴趣，你去培养他就是在提高你的	整体素养，提高你的竞争力，而且这些方式能在让你变强大的同时带走你的疲倦，你问下你自己，为什么别人会感兴趣你确不会，人都是有共性的，这项东西必然会吸引人必然有道理的，
    兴趣不是天生的。
    你去思考一下如果你对这个活动产生兴趣后会对自己带来什么，你自然就会去做。别总说 “我对什么都没兴趣”，先从 “试一次” 开始。
    
    
	
    你可以:
    1. 进行日常对话和问答
    2. 使用搜索工具获取最新信息
    3. 记住与用户的对话历史
    请保持回答专业、友好且准确。如果用户的问题需要最新信息,请使用搜索工具。
    
    """

    # 2. 构建提示词模板
    prompt = ChatPromptTemplate.from_messages(
        [
            ("system", system_prompt),  # 系统指令
            ("system", "以下是历史聊天记录：{summary}"),  # 对话历史摘要
            ("human", "{input}"),  # 用户输入
            MessagesPlaceholder(variable_name="agent_scratchpad")  # 智能体中间日志占位符
        ]
    )

    # 3. 获取LLM
    llm = get_default_llm()

    # 4. 创建OpenAI函数调用智能体（自动映射自然语言到工具调用，符合ReAct模式）
    # 参数：LLM实例、工具列表、提示词模板
    agent = create_openai_functions_agent(llm, tools, prompt)

    # 5. 配置智能体执行器（管理智能体的运行流程，如工具调用循环、错误处理）
    agent_excutor = AgentExecutor(
        agent=agent,  # 待执行的智能体实例
        tools=tools,  # 智能体可调用的工具列表
        verbose=True,  # 开启详细日志
        max_iterations=3,  # 限制工具调用最大迭代次数
        handle_parsing_errors=True  # 自动处理工具调用格式解析错误
    )

    # 返回配置好的智能体执行器（外部通过该实例发起对话）
    return agent_excutor


async def chat_with_agent(
        agent_excutor: AgentExecutor,
        chat_service: ChatService,
        account_no: str,
        input_text: str
) -> AsyncIterator[str]:
    """
    发起与智能体的异步对话，处理流式响应，并保存对话历史

    功能说明：
    1. 获取用户的对话历史摘要（通过ChatService）
    2. 异步流式调用智能体（获取逐段响应，避免全量等待）
    3. 保存完整对话到历史（用户输入+智能体回复）
    4. 逐字符返回智能体响应（供上层函数组装流式消息）

    Args:
        agent_excutor (AgentExecutor): 智能体执行器实例（由create_chat_agent创建）
        chat_service (ChatService): 对话服务实例（用于获取摘要、保存历史）
        account_no (str): 用户账号（唯一标识，用于关联对话历史）
        input_text (str): 用户当前输入的消息内容

    Returns:
        AsyncIterator[str]: 智能体响应的异步字符迭代器（逐字符返回，支持流式输出）

    Raises:
        ApiException: 对话过程中出现异常时抛出（如摘要获取失败、保存历史失败、智能体调用失败）
    """
    try:
        # 打印对话开始日志（控制台调试用）
        print(f"智能体开始对话{account_no}")

        # 1. 从对话服务获取当前用户的对话历史摘要
        summary = await chat_service.generate_summary(account_no)

        # 2. 异步流式调用智能体：astream()返回逐段响应
        async for chunk in agent_excutor.astream({
            "input": input_text,
            "summary": summary,
        }):
            # 提取智能体的输出内容
            if "output" in chunk:
                response = chunk["output"]  # 当前段智能体响应

                # 3. 保存完整对话到历史：异步保存用户输入+智能体回复到Redis
                success = await chat_service.save_chat_message(account_no, input_text, response)
                if not success:
                    raise ApiException(msg="保存对话消息失败")

                # 4. 逐字符返回当前段响应
                for token in response:
                    print(token)
                    yield token  # 异步返回单个字符
                    await asyncio.sleep(0.01)  # 模拟流式速度

    # 捕获所有异常
    except Exception as e:
        logger.error(f"智能体对话失败: {str(e)},用户:{account_no}", exc_info=True)
        raise ApiException(msg="智能体对话失败") from e


async def generate_stream_response(
        chat_service: ChatService,
        account_no: str,
        message: str
) -> AsyncIterator[str]:
    """
    生成符合前端要求的流式响应格式（组装字符为片段，按规范格式返回）

    功能说明：
    1. 创建智能体实例（加载工具列表）
    2. 调用chat_with_agent获取逐字符响应，组装为片段（按标点或长度分割）
    3. 用JsonData格式化片段（统一响应结构）
    4. 按SSE（Server-Sent Events）格式返回流式消息，最后发送完成标记

    Args:
        chat_service (ChatService): 对话服务实例（传递给chat_with_agent）
        account_no (str): 用户账号（传递给chat_with_agent，关联对话历史）
        message (str): 用户当前输入的消息内容（传递给chat_with_agent）

    Returns:
        AsyncIterator[str]: 符合SSE格式的异步流式响应迭代器（前端可直接接收解析）
    """
    # 1. 创建智能体实例：加载项目配置的工具列表（由get_chat_tools获取）
    agent = create_chat_agent(get_chat_tools())

    # 2. 初始化当前片段缓冲区（用于组装字符为有意义的片段，避免单字符传输）
    current_chunk = ""

    # 3. 调用chat_with_agent获取逐字符响应，组装为片段
    async for token in chat_with_agent(agent, chat_service, account_no, message):
        current_chunk += token  # 将当前字符追加到片段缓冲区

        # 片段分割规则：遇到标点符号或片段长度≥10时，发送当前片段
        if token in ["。", "?", "!", ";", ","] or len(current_chunk) >= 10:
            # 用JsonData格式化片段（
            response = JsonData.stream_data(data=current_chunk)
            # 按SSE格式组装消息
            yield f"data:{response.model_dump_json()}\n\n"
            # 重置片段缓冲区，准备接收下一段字符
            current_chunk = ""
            # 模拟流式速度
            await asyncio.sleep(0.1)

    # 4. 发送剩余片段
    if current_chunk:
        response = JsonData.stream_data(data=current_chunk)
        yield f"data:{response.model_dump_json()}\n\n"

    # 5. 发送流式结束标记
    yield "data:[DONE]\n\n"